import ctypes
import ctypes.wintypes
import datetime

WH_KEYBOARD_LL = 13
WM_KEYDOWN = 0x0100
user32 = ctypes.windll.user32

callbackfunc = ctypes.CFUNCTYPE(
    ctypes.c_long, ctypes.wintypes.INT, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM
)


class KBDLLHOOKSTRUCT(ctypes.Structure):
    _fields_ = [
        ("vkCode", ctypes.c_ulong),
        ("scanCode", ctypes.c_ulong),
        ("flags", ctypes.c_ulong),
        ("time", ctypes.c_ulong),
        ("dwExtraInfo", ctypes.c_ulong),
    ]


def ms2hhmmss(ms):
    return datetime.timedelta(milliseconds=ms)


def vkCode2utf(kbd):
    ToUnicodeEx=user32.ToUnicodeEx
    ToUnicodeEx.restype = ctypes.c_int
    ToUnicodeEx.argtypes = [
        ctypes.c_uint,
        ctypes.c_uint,
        ctypes.POINTER(ctypes.c_ubyte*256),
        ctypes.c_wchar_p,
        ctypes.c_int,
        ctypes.c_uint,
        ctypes.c_void_p
    ]
    GetKeyboardState=user32.GetKeyboardState
    GetKeyboardState.restype = ctypes.c_int
    GetKeyboardState.argtypes =  [ctypes.POINTER(ctypes.c_ubyte*256)]
    statebuff = (ctypes.c_ubyte*256)()
    pstate = ctypes.pointer(statebuff)
    GetKeyboardState(pstate)
    pwszBuff = ctypes.create_unicode_buffer(1)
    ToUnicodeEx(kbd.vkCode,kbd.scanCode,pstate,pwszBuff,1,0,0)
    return pwszBuff.value


def showmsg(kbd):
    # most time you donot need to use this function to show which char.
    # and this function is not so fast.may cause bug.
    # and do not suppert gamepad.
    print(vkCode2utf(kbd))

    hhmmss = str(ms2hhmmss(kbd.time))
    print(kbd.scanCode,hhmmss)


@callbackfunc
def LowLevelKeyboardProc(nCode, wParam, lParam):
    if nCode == 0:
        if wParam == WM_KEYDOWN:
            kbd = KBDLLHOOKSTRUCT.from_address(lParam)
            showmsg(kbd)
    return user32.CallNextHookEx(
        0, nCode, ctypes.wintypes.WPARAM(wParam), ctypes.wintypes.LPARAM(lParam)
    )


def main():
    hook_id = user32.SetWindowsHookExW(WH_KEYBOARD_LL, LowLevelKeyboardProc, None, 0)
    msg = ctypes.wintypes.MSG()
    # you need msg loop to keep the hook running.even if it's useless
    while user32.GetMessageW(ctypes.byref(msg), None, 0, 0) != 0:
        user32.TranslateMessage(msg)
        user32.DispatchMessageW(msg)


if __name__ == "__main__":
    main()
